/*
 * PeerDiscovery.cpp
 *
 *  Created on: Jan 30, 2015
 *      Author: toti
 */

#include <Connectivity/PeerDiscovery.hpp>


PeerDiscovery::PeerDiscovery(){

	config = Config::instance();

	logFileStream = new std::ofstream(config->getPeerDiscoveryFile());

	peerDiscoveryResponseList = new std::list<PeerDiscoveryResponseData_t*>();

	multicastAddressString = config->getMulticastAddress();
	unicastAddressString = config->getInterfaceAddress();

	inet_pton(AF_INET6, multicastAddressString.c_str(), &multicastAddress);
	inet_pton(AF_INET6, unicastAddressString.c_str(), &unicastAddress);

	multicastUDPPort = config->getMulticastPort();
	unicastUDPPort = config->getUnicastPort();

	multicastUDPServer = 0;
	unicastUDPServer = 0;
	multicastUDPClient = 0;

}


void PeerDiscovery::initPeerDiscovery(EPoll *epoll) {

	(*logFileStream) << "PeerDiscovery logfile:" << std::endl;
	(*logFileStream) << " Unicast address:\t " << unicastAddressString << std::endl;
	(*logFileStream) << " Unicast port:\t " << unicastUDPPort << std::endl;
	(*logFileStream) << " Multicast address:\t " << multicastAddressString << std::endl;
	(*logFileStream) << " Multicast port:\t " << multicastUDPPort << std::endl << std::endl;

	/*
	 * Datagram receiver
	 */
	multicastUDPServer = new UDPServer(multicastAddressString, multicastUDPPort);
	unicastUDPServer = new UDPServer(unicastAddressString, unicastUDPPort);

	/*
	 * Sender for answer
	 */
	multicastUDPClient = new UDPClient(multicastAddressString, multicastUDPPort);
	multicastUDPClient->initMulticast();

	/*
	 * Add to epoll
	 */

	epoll->addFd(multicastUDPServer->getSock(), (void*) multicastUDPServer);
	epoll->addFd(unicastUDPServer->getSock(), (void*) unicastUDPServer);

}


void PeerDiscovery::sendPeerDiscoveryRequest() {

	PeerDiscoveryRequestData_t peerDiscoveryRequestData;

	peerDiscoveryRequestData.address = unicastAddress;
	peerDiscoveryRequestData.port = htonl(atoi(unicastUDPPort.c_str()));

	multicastUDPClient->sendDatagram(sizeof(PeerDiscoveryRequestData_t), &peerDiscoveryRequestData);

}

int PeerDiscovery::receivePeerDiscoveryRequest(PeerDiscoveryRequestData_t *peerDiscoveryRequestData){

	return multicastUDPServer->receiveDatagram(sizeof(PeerDiscoveryRequestData_t), peerDiscoveryRequestData);

}

void PeerDiscovery::replyPeerDiscoveryRequest(PeerDiscoveryRequestData_t *peerDiscoveryRequestData){

	/*
	 * Receive request
	 */
	char receivedAddress[INET6_ADDRSTRLEN];
	uint32_t receivedPort;

	inet_ntop(AF_INET6, &(peerDiscoveryRequestData->address), receivedAddress, INET6_ADDRSTRLEN);
	receivedPort = ntohl(peerDiscoveryRequestData->port);

	if (memcmp(&(peerDiscoveryRequestData->address), &unicastAddress, sizeof(in6_addr))) {

		(*logFileStream) << Config::instance()->getClientName() << " Received PeerDiscoveryRequestData:\t " << receivedAddress << "     " << receivedPort << std::endl;

		/*
		 * Build response package
		 */
		PeerDiscoveryResponseData_t peerDiscoveryResponseData;

		in6_addr interfaceAddress;
		inet_pton(AF_INET6, Config::instance()->getInterfaceAddress().c_str(), &interfaceAddress);

		peerDiscoveryResponseData.address = interfaceAddress;
		peerDiscoveryResponseData.port = htonl(atoi(Config::instance()->getClientPort().c_str()));

		/*
		 * Send response package
		 */
		std::string unicastAddressString(receivedAddress);
		std::stringstream portStringStream;
		portStringStream << receivedPort;
		std::string portString = portStringStream.str();

		UDPClient peerDiscoveryResponseUDPClient(unicastAddressString, portString);
		peerDiscoveryResponseUDPClient.initUnicast();
		peerDiscoveryResponseUDPClient.sendDatagram(sizeof(PeerDiscoveryResponseData_t), (void*) &peerDiscoveryResponseData);

	}
}


PeerDiscovery::PeerDiscoveryResponseData_t *PeerDiscovery::receivePeerDiscoveryResponseData(){

	/*
	 * Receive request
	 */
	PeerDiscoveryResponseData_t *peerDiscoveryResponseData = new PeerDiscoveryResponseData_t;

	if (unicastUDPServer->receiveDatagram(sizeof(PeerDiscoveryResponseData_t), peerDiscoveryResponseData) > 0) {

		char receivedAddress[INET6_ADDRSTRLEN];
		uint32_t receivedPort;

		inet_ntop(AF_INET6, &(peerDiscoveryResponseData->address), receivedAddress, INET6_ADDRSTRLEN);
		receivedPort = ntohl(peerDiscoveryResponseData->port);

		if (memcmp(&peerDiscoveryResponseData->address, &unicastAddress, sizeof(in6_addr))) {

			std::string receivedAddressString(receivedAddress);

			(*logFileStream) << Config::instance()->getClientName() << " Received PeerDiscoveryResponseData:\t " << receivedAddress << "     " << receivedPort << std::endl;

			pushDiscoveryResponseList(peerDiscoveryResponseData);

			return peerDiscoveryResponseData;

		}else{

			free(peerDiscoveryResponseData);

		}

	}

	return 0;

}

UDPServer *PeerDiscovery::getMulticastUDPServer(){
	return multicastUDPServer;
}

UDPServer *PeerDiscovery::getUnicastUDPServer(){
	return unicastUDPServer;
}

void PeerDiscovery::clearDiscoveredPeersList() {
	PeerDiscoveryResponseData_t *peerDiscoveryResponseData;

	while (peerDiscoveryResponseList->size()) {
		peerDiscoveryResponseData = peerDiscoveryResponseList->front();
		delete (peerDiscoveryResponseData);
		peerDiscoveryResponseList->pop_front();
	}
}

void PeerDiscovery::pushDiscoveryResponseList(PeerDiscoveryResponseData_t *peerDiscoveryResponseData) {
	peerDiscoveryResponseList->push_back(peerDiscoveryResponseData);
}

PeerDiscovery::PeerDiscoveryResponseData_t *PeerDiscovery::popDiscoveryResponseList() {

	PeerDiscoveryResponseData_t *peerDiscoveryResponseData;

	if (peerDiscoveryResponseList->size()) {
		peerDiscoveryResponseData = peerDiscoveryResponseList->front();
		peerDiscoveryResponseList->pop_front();
	}

	return peerDiscoveryResponseData;
}
